/** @file
  Implement the driver binding protocol for Asix AX88772 Ethernet driver.
                     
  Copyright (c) 2011-2013, Intel Corporation. All rights reserved.
  SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include "Ax88772.h"

ASIX_DONGLE ASIX_DONGLES[] = {
  { 0x05AC, 0x1402, FLAG_TYPE_AX88772 }, // Apple USB Ethernet Adapter
  // ASIX 88772B
  { 0x0B95, 0x772B, FLAG_TYPE_AX88772B | FLAG_EEPROM_MAC },
  { 0x0000, 0x0000, FLAG_NONE }   // END - Do not remove
};

/**
  Verify the controller type

  @param [in] pThis                Protocol instance pointer.
  @param [in] Controller           Handle of device to test.
  @param [in] pRemainingDevicePath Not used.

  @retval EFI_SUCCESS          This driver supports this device.
  @retval other                This driver does not support this device.

**/
EFI_STATUS
EFIAPI
DriverSupported (
  IN EFI_DRIVER_BINDING_PROTOCOL * pThis,
  IN EFI_HANDLE Controller,
  IN EFI_DEVICE_PATH_PROTOCOL * pRemainingDevicePath
  )
{
  EFI_USB_DEVICE_DESCRIPTOR Device;
  EFI_USB_IO_PROTOCOL * pUsbIo;
  EFI_STATUS Status;
  UINT32 Index;

  //
  //  Connect to the USB stack
  //
  Status = gBS->OpenProtocol (
                  Controller,
                  &gEfiUsbIoProtocolGuid,
                  (VOID **) &pUsbIo,
                  pThis->DriverBindingHandle,         
                  Controller,
                  EFI_OPEN_PROTOCOL_BY_DRIVER
                  );
  if (!EFI_ERROR ( Status )) {

    //
    //  Get the interface descriptor to check the USB class and find a transport
    //  protocol handler.
    //
    Status = pUsbIo->UsbGetDeviceDescriptor ( pUsbIo, &Device );
    if (EFI_ERROR ( Status )) {
    	Status = EFI_UNSUPPORTED;
    }
    else {
      //
      //  Validate the adapter
      //
      for (Index = 0; ASIX_DONGLES[Index].VendorId != 0; Index++) {
        if (ASIX_DONGLES[Index].VendorId == Device.IdVendor &&
            ASIX_DONGLES[Index].ProductId == Device.IdProduct) {
              DEBUG ((EFI_D_INFO, "Found the AX88772B\r\n"));
              break;
        }
      }

      if (ASIX_DONGLES[Index].VendorId == 0)
         Status = EFI_UNSUPPORTED;
    }
   
    //
    //  Done with the USB stack
    //
    gBS->CloseProtocol (
           Controller,
           &gEfiUsbIoProtocolGuid,
           pThis->DriverBindingHandle,
           Controller
           );
  }
  return Status;
}


/**
  Start this driver on Controller by opening UsbIo and DevicePath protocols.
  Initialize PXE structures, create a copy of the Controller Device Path with the
  NIC's MAC address appended to it, install the NetworkInterfaceIdentifier protocol
  on the newly created Device Path.

  @param [in] pThis                Protocol instance pointer.
  @param [in] Controller           Handle of device to work with.
  @param [in] pRemainingDevicePath Not used, always produce all possible children.

  @retval EFI_SUCCESS          This driver is added to Controller.
  @retval other                This driver does not support this device.

**/
EFI_STATUS
EFIAPI
DriverStart (
  IN EFI_DRIVER_BINDING_PROTOCOL * pThis,
  IN EFI_HANDLE Controller,
  IN EFI_DEVICE_PATH_PROTOCOL * pRemainingDevicePath
  )
{

	EFI_STATUS						Status;
	NIC_DEVICE						*pNicDevice;
	UINTN							LengthInBytes;
	EFI_DEVICE_PATH_PROTOCOL        *ParentDevicePath = NULL;
	MAC_ADDR_DEVICE_PATH            MacDeviceNode;
        EFI_USB_DEVICE_DESCRIPTOR       Device;
        UINT32                          Index;

  //
	//  Allocate the device structure
	//
	LengthInBytes = sizeof ( *pNicDevice );
	Status = gBS->AllocatePool (
                  EfiRuntimeServicesData,
                  LengthInBytes,
                  (VOID **) &pNicDevice
                  );

	if (EFI_ERROR (Status)) {
		DEBUG ((EFI_D_ERROR, "gBS->AllocatePool:pNicDevice ERROR Status = %r\n", Status));
		goto EXIT;
	}
	
	//
  //  Set the structure signature
  //
  ZeroMem ( pNicDevice, LengthInBytes );
  pNicDevice->Signature = DEV_SIGNATURE;

	Status = gBS->OpenProtocol (
                    Controller,
                    &gEfiUsbIoProtocolGuid,
                    (VOID **) &pNicDevice->pUsbIo,
                    pThis->DriverBindingHandle,
                    Controller,
                    EFI_OPEN_PROTOCOL_BY_DRIVER
                    );

	if (EFI_ERROR (Status)) {
		DEBUG ((EFI_D_ERROR, "gBS->OpenProtocol:EFI_USB_IO_PROTOCOL ERROR Status = %r\n", Status));
		gBS->FreePool ( pNicDevice );
		goto EXIT;
	}

	//
  //  Initialize the simple network protocol
  //
	Status = SN_Setup ( pNicDevice );

	if (EFI_ERROR(Status)){
	   DEBUG ((EFI_D_ERROR, "SN_Setup ERROR Status = %r\n", Status));
	   gBS->CloseProtocol (
					Controller,
					&gEfiUsbIoProtocolGuid,
					pThis->DriverBindingHandle,
					Controller
					);
		  gBS->FreePool ( pNicDevice );
		  goto EXIT;
  }

  Status = pNicDevice->pUsbIo->UsbGetDeviceDescriptor ( pNicDevice->pUsbIo, &Device );
  if (EFI_ERROR ( Status )) {
     gBS->CloseProtocol (
               Controller,
               &gEfiUsbIoProtocolGuid,
               pThis->DriverBindingHandle,
               Controller
               );
     gBS->FreePool ( pNicDevice );
              goto EXIT;
  } else {
      //
      //  Validate the adapter
      //
      for (Index = 0; ASIX_DONGLES[Index].VendorId != 0; Index++) {
          if (ASIX_DONGLES[Index].VendorId == Device.IdVendor &&
              ASIX_DONGLES[Index].ProductId == Device.IdProduct) {
                break;
          }
      }

      if (ASIX_DONGLES[Index].VendorId == 0) {
         gBS->CloseProtocol (
                   Controller,
                   &gEfiUsbIoProtocolGuid,
                   pThis->DriverBindingHandle,
                   Controller
                   );
          gBS->FreePool ( pNicDevice );
                   goto EXIT;
      }

      pNicDevice->Flags = ASIX_DONGLES[Index].Flags;
  }

	//
  // Set Device Path
  //  			
  Status = gBS->OpenProtocol (
                  Controller,
                  &gEfiDevicePathProtocolGuid,
                  (VOID **) &ParentDevicePath,
				          pThis->DriverBindingHandle,
                  Controller,
                  EFI_OPEN_PROTOCOL_BY_DRIVER
                  );
	if (EFI_ERROR(Status)) {
        DEBUG ((EFI_D_ERROR, "gBS->OpenProtocol:EFI_DEVICE_PATH_PROTOCOL error. Status = %r\n",
            Status));        
		    gBS->CloseProtocol (
					Controller,
					&gEfiUsbIoProtocolGuid,
					pThis->DriverBindingHandle,
					Controller
					);
		  gBS->FreePool ( pNicDevice );
		  goto EXIT;
	}

  ZeroMem (&MacDeviceNode, sizeof (MAC_ADDR_DEVICE_PATH));
  MacDeviceNode.Header.Type = MESSAGING_DEVICE_PATH;
  MacDeviceNode.Header.SubType = MSG_MAC_ADDR_DP;

  SetDevicePathNodeLength (&MacDeviceNode.Header, sizeof (MAC_ADDR_DEVICE_PATH));
      			
  CopyMem (&MacDeviceNode.MacAddress,
      								&pNicDevice->SimpleNetworkData.CurrentAddress,
      								PXE_HWADDR_LEN_ETHER);
      								
  MacDeviceNode.IfType = pNicDevice->SimpleNetworkData.IfType;

  pNicDevice->MyDevPath = AppendDevicePathNode (
                                          ParentDevicePath,
                                          (EFI_DEVICE_PATH_PROTOCOL *) &MacDeviceNode
                                          );

	pNicDevice->Controller = NULL;

	//
  //  Install both the simple network and device path protocols.
  //
  Status = gBS->InstallMultipleProtocolInterfaces (
                          &pNicDevice->Controller,
                          &gEfiCallerIdGuid,
                          pNicDevice,
                          &gEfiSimpleNetworkProtocolGuid,            
                          &pNicDevice->SimpleNetwork,
						              &gEfiDevicePathProtocolGuid,
						              pNicDevice->MyDevPath,
                          NULL
                          );

	if (EFI_ERROR(Status)){
		DEBUG ((EFI_D_ERROR, "gBS->InstallMultipleProtocolInterfaces error. Status = %r\n",
            Status)); 
		gBS->CloseProtocol (
					               Controller,
					               &gEfiDevicePathProtocolGuid,
					               pThis->DriverBindingHandle,
					               Controller);
	   gBS->CloseProtocol (
					Controller,
					&gEfiUsbIoProtocolGuid,
					pThis->DriverBindingHandle,
					Controller
					);
		  gBS->FreePool ( pNicDevice );
		  goto EXIT;
	}

	//
	// Open For Child Device
	//
	Status = gBS->OpenProtocol (                                                                         
                  Controller,
                  &gEfiUsbIoProtocolGuid,
                  (VOID **) &pNicDevice->pUsbIo,
                  pThis->DriverBindingHandle,
                  pNicDevice->Controller,
                  EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER
                  );

	if (EFI_ERROR(Status)){
	   gBS->UninstallMultipleProtocolInterfaces (
              &pNicDevice->Controller,
                          &gEfiCallerIdGuid,
                          pNicDevice,
                          &gEfiSimpleNetworkProtocolGuid,            
                          &pNicDevice->SimpleNetwork,
						              &gEfiDevicePathProtocolGuid,
						              pNicDevice->MyDevPath,
                          NULL
                          );
		gBS->CloseProtocol (
					               Controller,
					               &gEfiDevicePathProtocolGuid,
					               pThis->DriverBindingHandle,
					               Controller);
	   gBS->CloseProtocol (
					Controller,
					&gEfiUsbIoProtocolGuid,
					pThis->DriverBindingHandle,
					Controller
					);
		  gBS->FreePool ( pNicDevice );
	}

EXIT:
	return Status;

}

/**
  Stop this driver on Controller by removing NetworkInterfaceIdentifier protocol and
  closing the DevicePath and PciIo protocols on Controller.

  @param [in] pThis                Protocol instance pointer.
  @param [in] Controller           Handle of device to stop driver on.
  @param [in] NumberOfChildren     How many children need to be stopped.
  @param [in] pChildHandleBuffer   Not used.

  @retval EFI_SUCCESS          This driver is removed Controller.
  @retval EFI_DEVICE_ERROR     The device could not be stopped due to a device error.
  @retval other                This driver was not removed from this device.

**/
EFI_STATUS
EFIAPI
DriverStop (
  IN  EFI_DRIVER_BINDING_PROTOCOL * pThis,
  IN  EFI_HANDLE Controller,
  IN  UINTN NumberOfChildren,
  IN  EFI_HANDLE * ChildHandleBuffer
  )
{
		BOOLEAN                                   AllChildrenStopped;
		UINTN                                     Index;
		EFI_SIMPLE_NETWORK_PROTOCOL				  *SimpleNetwork;
		EFI_STATUS                                Status = EFI_SUCCESS;
		NIC_DEVICE								  *pNicDevice;
		
		//
		// Complete all outstanding transactions to Controller.
		// Don't allow any new transaction to Controller to be started.
		//
		if (NumberOfChildren == 0) {
		
		  Status = gBS->OpenProtocol (
				                Controller,
				                &gEfiSimpleNetworkProtocolGuid,
				                (VOID **) &SimpleNetwork,
				                pThis->DriverBindingHandle,
				                Controller,
				                EFI_OPEN_PROTOCOL_GET_PROTOCOL
				                );
				                
			if (EFI_ERROR(Status)) {
        //
        // This is a 2nd type handle(multi-lun root), it needs to close devicepath
        // and usbio protocol.
        //
        gBS->CloseProtocol (
            Controller,
            &gEfiDevicePathProtocolGuid,
            pThis->DriverBindingHandle,
            Controller
            );
        gBS->CloseProtocol (
            Controller,
            &gEfiUsbIoProtocolGuid,
            pThis->DriverBindingHandle,
            Controller
            );
        return EFI_SUCCESS;
      }
      
      pNicDevice = DEV_FROM_SIMPLE_NETWORK ( SimpleNetwork );
      
      Status = gBS->UninstallMultipleProtocolInterfaces (
				                  Controller,				                  
				                  &gEfiCallerIdGuid,
                          pNicDevice,
                          &gEfiSimpleNetworkProtocolGuid,            
                          &pNicDevice->SimpleNetwork,
						              &gEfiDevicePathProtocolGuid,
						              pNicDevice->MyDevPath,
                          NULL
                          );
                          
      if (EFI_ERROR (Status)) {
        return Status;
      }
		  //
		  // Close the bus driver
		  //
		  Status = gBS->CloseProtocol (
		                  Controller,
		                  &gEfiDevicePathProtocolGuid,
		                  pThis->DriverBindingHandle,
		                  Controller
		                  );

		  if (EFI_ERROR(Status)){
          DEBUG ((EFI_D_ERROR, "driver stop: gBS->CloseProtocol:EfiDevicePathProtocol error. Status %r\n", Status));
		  }

		  Status = gBS->CloseProtocol (
		                  Controller,
		                  &gEfiUsbIoProtocolGuid,
		                  pThis->DriverBindingHandle,
		                  Controller
		                  );

		  if (EFI_ERROR(Status)){
          DEBUG ((EFI_D_ERROR, "driver stop: gBS->CloseProtocol:EfiUsbIoProtocol error. Status %r\n", Status));
		  }
      return EFI_SUCCESS;
		} 
		AllChildrenStopped = TRUE;

		for (Index = 0; Index < NumberOfChildren; Index++) {

				Status = gBS->OpenProtocol (
				                ChildHandleBuffer[Index],
				                &gEfiSimpleNetworkProtocolGuid,
				                (VOID **) &SimpleNetwork,
				                pThis->DriverBindingHandle,
				                Controller,
				                EFI_OPEN_PROTOCOL_GET_PROTOCOL
				                );
				                
				if (EFI_ERROR (Status)) {
          AllChildrenStopped = FALSE;
          DEBUG ((EFI_D_ERROR, "Fail to stop No.%d multi-lun child handle when opening SimpleNetwork\n", (UINT32)Index));
          continue;
        } 
        
        pNicDevice = DEV_FROM_SIMPLE_NETWORK ( SimpleNetwork );
        
        gBS->CloseProtocol (
				                    Controller,
				                    &gEfiUsbIoProtocolGuid,
				                    pThis->DriverBindingHandle,
				                    ChildHandleBuffer[Index]
				                    ); 
				                    
				Status = gBS->UninstallMultipleProtocolInterfaces (
				                  ChildHandleBuffer[Index],				                  
				                  &gEfiCallerIdGuid,
                          pNicDevice,
                          &gEfiSimpleNetworkProtocolGuid,            
                          &pNicDevice->SimpleNetwork,
						              &gEfiDevicePathProtocolGuid,
						              pNicDevice->MyDevPath,
                          NULL
                          );
                          
        if (EFI_ERROR (Status)) {
            Status = gBS->OpenProtocol (                                                                         
                  Controller,
                  &gEfiUsbIoProtocolGuid,
                  (VOID **) &pNicDevice->pUsbIo,
                  pThis->DriverBindingHandle,
                  ChildHandleBuffer[Index],
                  EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER
                  );
        }
        else {
            int i;
            RX_PKT * pCurr = pNicDevice->QueueHead;
            RX_PKT * pFree;
            
            for ( i = 0 ; i < MAX_QUEUE_SIZE ; i++) {
                 if ( NULL != pCurr ) {
                    pFree = pCurr;
                    pCurr = pCurr->pNext;
                    gBS->FreePool (pFree);
                 }
            }
            
            if ( NULL != pNicDevice->pRxTest)
						    gBS->FreePool (pNicDevice->pRxTest);

					 if ( NULL != pNicDevice->pTxTest)
						    gBS->FreePool (pNicDevice->pTxTest);

           if ( NULL != pNicDevice->MyDevPath)
					       gBS->FreePool (pNicDevice->MyDevPath);
		  
				    if ( NULL != pNicDevice)
                  gBS->FreePool (pNicDevice);
        }
    }
        
        if (!AllChildrenStopped) {
                return EFI_DEVICE_ERROR;
        }
        return EFI_SUCCESS;
}


/**
  Driver binding protocol declaration
**/
EFI_DRIVER_BINDING_PROTOCOL  gDriverBinding = {
  DriverSupported,
  DriverStart,
  DriverStop,
  0xa,
  NULL,
  NULL
};


/**
  Ax88772 driver unload routine.

  @param [in] ImageHandle       Handle for the image.

  @retval EFI_SUCCESS           Image may be unloaded

**/
EFI_STATUS
EFIAPI
DriverUnload (
  IN EFI_HANDLE ImageHandle
  )
{
  UINTN BufferSize;
  UINTN Index;
  UINTN Max;
  EFI_HANDLE * pHandle;
  EFI_STATUS Status;

  //
  //  Determine which devices are using this driver
  //
  BufferSize = 0;
  pHandle = NULL;
  Status = gBS->LocateHandle (
                  ByProtocol,
                  &gEfiCallerIdGuid,
                  NULL,
                  &BufferSize,
                  NULL );
  if ( EFI_BUFFER_TOO_SMALL == Status ) {
    for ( ; ; ) {
      //
      //  One or more block IO devices are present
      //
      Status = gBS->AllocatePool (
                      EfiRuntimeServicesData,
                      BufferSize,
                      (VOID **) &pHandle
                      );
      if ( EFI_ERROR ( Status )) {
        DEBUG ((EFI_D_ERROR, "Insufficient memory, failed handle buffer allocation\r\n"));
        break;
      }

      //
      //  Locate the block IO devices
      //
      Status = gBS->LocateHandle (
                      ByProtocol,
                      &gEfiCallerIdGuid,
                      NULL,
                      &BufferSize,
                      pHandle );
      if ( EFI_ERROR ( Status )) {
        //
        //  Error getting handles
        //
        break;
      }
      
      //
      //  Remove any use of the driver
      //
      Max = BufferSize / sizeof ( pHandle[ 0 ]);
      for ( Index = 0; Max > Index; Index++ ) {
        Status = DriverStop ( &gDriverBinding,
                              pHandle[ Index ],
                              0,
                              NULL );
        if ( EFI_ERROR ( Status )) {
          DEBUG ((EFI_D_ERROR, "WARNING - Failed to shutdown the driver on handle %08x\r\n", pHandle[ Index ]));
          break;
        }
      }
      break;
    }
  }
  else {
    if ( EFI_NOT_FOUND == Status ) {
      //
      //  No devices were found
      //
      Status = EFI_SUCCESS;
    }
  }

  //
  //  Free the handle array          
  //
  if ( NULL != pHandle ) {
    gBS->FreePool ( pHandle );
  }

  //
  //  Remove the protocols installed by the EntryPoint routine.
  //
  if ( !EFI_ERROR ( Status )) {
    gBS->UninstallMultipleProtocolInterfaces (
            ImageHandle,
            &gEfiDriverBindingProtocolGuid,
            &gDriverBinding,                              
            &gEfiComponentNameProtocolGuid,
            &gComponentName,
            &gEfiComponentName2ProtocolGuid,
            &gComponentName2,
            NULL
            );

    DEBUG (( DEBUG_POOL | DEBUG_INIT | DEBUG_INFO,
            "Removed:   gEfiComponentName2ProtocolGuid from 0x%08x\r\n",
            ImageHandle ));
    DEBUG (( DEBUG_POOL | DEBUG_INIT | DEBUG_INFO,
              "Removed:   gEfiComponentNameProtocolGuid from 0x%08x\r\n",
              ImageHandle ));
    DEBUG (( DEBUG_POOL | DEBUG_INIT | DEBUG_INFO,
              "Removed:   gEfiDriverBindingProtocolGuid from 0x%08x\r\n",
              ImageHandle ));

  }

  return Status;
}


/**
Ax88772 driver entry point.

@param [in] ImageHandle       Handle for the image.
@param [in] pSystemTable      Address of the system table.

@retval EFI_SUCCESS           Image successfully loaded.

**/
EFI_STATUS
EFIAPI
EntryPoint (
  IN EFI_HANDLE ImageHandle,
  IN EFI_SYSTEM_TABLE * pSystemTable
  )
{
  EFI_STATUS    Status;

  //
  //  Add the driver to the list of drivers
  //
  Status = EfiLibInstallDriverBindingComponentName2 (
             ImageHandle,
             pSystemTable,
             &gDriverBinding,
             ImageHandle,
             &gComponentName,
             &gComponentName2
             );
  if ( !EFI_ERROR ( Status )) {
    DEBUG ((EFI_D_INFO, "Installed: gEfiDriverBindingProtocolGuid on   0x%08x\r\n",
              ImageHandle));
    DEBUG ((EFI_D_INFO, "Installed: gEfiComponentNameProtocolGuid on   0x%08x\r\n",
              ImageHandle));
    DEBUG ((EFI_D_INFO,"Installed: gEfiComponentName2ProtocolGuid on   0x%08x\r\n",
              ImageHandle ));

  }
  return Status;
}
